package com.zmykj.hik.hikService.Service.Storage;

import java.io.*;
import java.nio.ByteBuffer;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.zmykj.hik.hikService.Common.CommonClass.IntPointer;
import com.zmykj.hik.hikService.Common.CommonClass.StringPointer;
import com.zmykj.hik.hikService.Service.Alarm.ISUPAMSByJNA;
import com.zmykj.hik.hikService.Service.Storage.ISUPSSByJNA.EHomeSSMsgCallBack;
import com.zmykj.hik.hikService.Service.Storage.ISUPSSByJNA.EHomeSSStorageCallBack;
import com.zmykj.hik.hikService.Service.Storage.ISUPSSByJNA.EHomeSSRWCallBack;
import com.zmykj.hik.hikService.Service.Storage.ISUPSSByJNA.NET_EHOME_SS_LISTEN_PARAM;
import com.zmykj.hik.hikService.Service.Storage.ISUPSSByJNA.NET_EHOME_SS_TOMCAT_MSG;
import com.zmykj.hik.hikService.Common.CommonMethod;
import com.sun.jna.Native;
import com.sun.jna.NativeLong;
import com.sun.jna.Pointer;

public class ESS {
	private static ISUPSSByJNA m_SSInstance = null;
	
	private static final String SS_PATH         = "./StorageServer";
	private static final String SS_STORAGE_PATH = "./StorageServer/Storage";
	private static final String SS_MESSAGE_PATH = "./StorageServer/Message";
	private static EHomeSSStorageCallBack m_fEHomeSStorageCallBack = null;
	private static EHomeSSMsgCallBack m_fEHomeSSMsgCallBack = null;
	private static EHomeSSRWCallBack m_fEHomeSSRWCallBack = null;
	private static Map<String, String> m_mapPictureStorageList = new ConcurrentHashMap<String, String>();
	private NativeLong m_lSSListenHandle = new NativeLong(-1);
	private static NET_EHOME_SS_LISTEN_PARAM m_struSSListenParam = new NET_EHOME_SS_LISTEN_PARAM();
	private boolean m_bIsRunning = false;
	
	public ESS() {
        if(m_SSInstance == null) {
            if(!CreateSSInstance())
            {
                System.out.println("Load SS fail");
                return;
            }
        }

        //only in linux platom support config sdk path
        if(CommonMethod.isLinux()){
            String sSDKPath = CommonMethod.GetLibPathByArch();
            StringPointer strPointer = new StringPointer(sSDKPath);
            strPointer.write();
            if (m_SSInstance.NET_ESS_SetSDKInitCfg(ISUPSSByJNA.NET_EHOME_SS_INIT_CFG_SDK_PATH, strPointer.getPointer()))
                System.out.println("SDKPath:" + sSDKPath);
            else
                System.out.println("SDKPath:" + sSDKPath);
            strPointer.read();
        }

        //Set the address of the public network (only valid when the private network is mapped to the public network).
        CommonMethod.GetServerInfo().struPictureSever.write();
        if(m_SSInstance.NET_ESS_SetSDKInitCfg(ISUPSSByJNA.NET_EHOME_SS_INIT_CFG_PUBLIC_IP_PORT,
                CommonMethod.GetServerInfo().struPictureSever.getPointer())){
            CommonMethod.logRecord("INFO", "NET_EHOME_SS_INIT_CFG_PUBLIC_IP_PORT Success, IP:" +
                    CommonMethod.byteToString(CommonMethod.GetServerInfo().struPictureSever.szIP) + "Port:" +
                    CommonMethod.GetServerInfo().struPictureSever.wPort);
        }else{
            CommonMethod.logRecord("ERROR","NET_EHOME_SS_INIT_CFG_PUBLIC_IP_PORT Fail");
        }
        
        //Set openssl dll path
        String sLibCryptoPath = CommonMethod.GetLibPathByArch();
        String sLibSslPath = CommonMethod.GetLibPathByArch();
        if(CommonMethod.isLinux()){
            sLibCryptoPath += "libcrypto.so";
            sLibSslPath += "libssl.so";
        }else{
            sLibCryptoPath += "libeay32.dll";
            sLibSslPath += "ssleay32.dll";
        }
        StringPointer pLibCryptoPath = new StringPointer(sLibCryptoPath);
        StringPointer pLibSslPath = new StringPointer(sLibSslPath);
        pLibCryptoPath.write();
        pLibSslPath.write();

        if(m_SSInstance.NET_ESS_SetSDKInitCfg(ISUPSSByJNA.NET_EHOME_SS_INIT_CFG_LIBEAY_PATH,
                pLibCryptoPath.getPointer())){
            CommonMethod.logRecord("INFO", "LIBEAY load success: " + sLibCryptoPath);
        }else{
            CommonMethod.logRecord("ERROR","LIBEAY load fail" + sLibCryptoPath);
        }

        if(m_SSInstance.NET_ESS_SetSDKInitCfg(ISUPSSByJNA.NET_EHOME_SS_INIT_CFG_SSLEAY_PATH,
                pLibSslPath.getPointer())){
            CommonMethod.logRecord("INFO", "SSLEAY load success: " + sLibSslPath);
        }else{
            CommonMethod.logRecord("ERROR","SSLEAY load fail" + sLibSslPath);
        }

		if(m_SSInstance.NET_ESS_Init()) {
            CommonMethod.logRecord("INFO", "[NET_ESS_Init]->ESS initialize successfully");

            // open ESS sdk log
            m_SSInstance.NET_ESS_SetLogToFile(3, "./EHomeSdkLog", true);

            // register ESS registetr message callback function
            if (m_fEHomeSStorageCallBack == null) {
                m_fEHomeSStorageCallBack = new FEHomeSSStorageCallBack();
                m_struSSListenParam.fnSStorageCb = m_fEHomeSStorageCallBack;
            }

            if(CommonMethod.GetListenInfo().byCallbackType == 1)
            {
                if (m_fEHomeSSRWCallBack == null) {
                    m_fEHomeSSRWCallBack = new FEHomeSSRWCallBack();
                    m_struSSListenParam.fnSSRWCb = m_fEHomeSSRWCallBack;
                }
            }

            if (m_fEHomeSSMsgCallBack == null) {
                m_fEHomeSSMsgCallBack = new FEHomeSSMsgCallBack();
                m_struSSListenParam.fnSSMsgCb = m_fEHomeSSMsgCallBack;
            }

            CommonMethod.ByteCopy(CommonMethod.GetListenInfo().struSSListenParam.struAddress.szIP, m_struSSListenParam.struAddress.szIP);
            m_struSSListenParam.struAddress.wPort = CommonMethod.GetListenInfo().struSSListenParam.struAddress.wPort;
            CommonMethod.ByteCopy(CommonMethod.GetListenInfo().struSSListenParam.szAccessKey, m_struSSListenParam.szAccessKey);
            CommonMethod.ByteCopy(CommonMethod.GetListenInfo().struSSListenParam.szSecretKey, m_struSSListenParam.szSecretKey);
            CommonMethod.ByteCopy(CommonMethod.GetListenInfo().struSSListenParam.szKMS_UserName, m_struSSListenParam.szKMS_UserName);
            CommonMethod.ByteCopy(CommonMethod.GetListenInfo().struSSListenParam.szKMS_Password, m_struSSListenParam.szKMS_Password);
            m_struSSListenParam.byHttps = CommonMethod.GetServerInfo().byClouldHttps;
            m_struSSListenParam.bySecurityMode = CommonMethod.GetListenInfo().struSSListenParam.bySecurityMode;
            m_struSSListenParam.write();

            m_lSSListenHandle = m_SSInstance.NET_ESS_StartListen(m_struSSListenParam);
            if (m_lSSListenHandle.longValue() < 0) {
                CommonMethod.logRecord("ERROR",
                        "[NET_ESS_StartListen]->SS start listen failed, errorCode is " + m_SSInstance.NET_ESS_GetLastError());
            } else {
                m_bIsRunning = true;
                CommonMethod.logRecord("INFO", "[NET_ESS_StartListen]->SS start listen successfully; port:" +
                        m_struSSListenParam.struAddress.wPort);
            }
		}else{
            CommonMethod.logRecord("ERROR", "[NET_ESS_Init]->ESS initialize Failed, errorCode:" + m_SSInstance.NET_ESS_GetLastError());
        }
	}

	public void StopESS() {
		boolean bIsStopSSListen = m_SSInstance.NET_ESS_StopListen(m_lSSListenHandle);
		if (bIsStopSSListen) {
            m_bIsRunning = false;
			CommonMethod.logRecord("INFO", "[NET_ESS_StopListen]->SS stop listen successfully");
			m_lSSListenHandle.setValue(-1);
		} else {
			CommonMethod.logRecord("ERROR",
					"[NET_ESS_StopListen]->AMS stop listen failed, errorCode is:" + m_SSInstance.NET_ESS_GetLastError());
		}
		// SDK cleanUp
        m_SSInstance.NET_ESS_Fini();
		CommonMethod.logRecord("INFO", "[NET_ESS_Fini]->EHomeSS release resource successfully");
	}

	//RW callback, the user needs to maintain the correspondence between the image file path and the image url by themselves
    //If the user defines the storage callback and RW callback at the same time, the Storage callback will not take effect
	private class FEHomeSSRWCallBack implements EHomeSSRWCallBack{
	    @Override
        public boolean invoke(NativeLong iHandle, byte byAct, String pFileName, Pointer pFileBuf, IntPointer dwFileLen, Pointer pFileUrl, Pointer pUser) {
            //write file(upload file to local storage server)
            if(byAct == 0)
            {
                String strFilePath = "";
                if(pFileName != null)
                {
                    strFilePath = SS_STORAGE_PATH + "/" + pFileName;
                }

                byte[] byFileURL = new byte[256];
                if(pFileUrl != null)
                {
                    CommonMethod.ByteCopy(pFileUrl.getByteArray(0, 256), byFileURL);
                }

                File myPath = new File(SS_STORAGE_PATH);
                if ( !myPath.exists()){
                    myPath.mkdir();
                    System.out.println("mkdir path："+ SS_STORAGE_PATH);
                }

                if (dwFileLen.GetData() > 0 && pFileBuf != null)
                {
                    FileOutputStream fout;
                    try {
                        fout = new FileOutputStream(strFilePath);
                        //将字节写入文件
                        long offset = 0;
                        ByteBuffer buffers = pFileBuf.getByteBuffer(offset, dwFileLen.GetData());
                        byte [] bytes = new byte[dwFileLen.GetData()];
                        buffers.rewind();
                        buffers.get(bytes);
                        fout.write(bytes);
                        fout.close();
                    } catch (FileNotFoundException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }

                String sPictureUrl = CommonMethod.byteToString(byFileURL);
                System.out.println("Picture url: " + sPictureUrl);
                //Here as an example, we use hashmap to save the relationship between image url and file path.
                //production scene, it is recommended to use a key value similar to redis to save the database
                m_mapPictureStorageList.put(sPictureUrl, strFilePath);
            }

            //read file(download file from local storage server)
            if(byAct == 1)
            {
                String strFilePath = "";
                if(pFileUrl != null)
                {
                    byte[] byFileURL = new byte[256];
                    CommonMethod.ByteCopy(pFileUrl.getByteArray(0, 256), byFileURL);
                    String sPicUrl = CommonMethod.byteToString(byFileURL);
                    //remove [token words] of the kms url
                    if(sPicUrl.contains("kms"))
                    {
                        sPicUrl = sPicUrl.split("&token=")[0];
                    }
                    System.out.println("download file url: " + sPicUrl);
                    if(m_mapPictureStorageList.containsKey(sPicUrl))
                    {
                        strFilePath = m_mapPictureStorageList.get(sPicUrl);
                    }
                    System.out.println("Picture Path:" + strFilePath);
                }
                //according sql to get the image file path
                try {
                    FileInputStream picfile = null;
                    int picdataLength = 0;

                    picfile = new FileInputStream(new File(strFilePath));
                    picdataLength = picfile.available();

                    if(picdataLength < 0)
                    {
                        System.out.println("input file dataSize < 0");
                        return false;
                    }

                    //There will be two callbacks here
                    if (pFileBuf == null) //if(pFileBuf == null) => 1st callback
                    {
                       dwFileLen.SetData(picdataLength);  //only return file size
                    }

                    if(pFileBuf != null)    //if(pFileBuf != null) => 2nd callback
                    {
                        //return file content
                        StringPointer ptrpicByte = new StringPointer(picdataLength);
                        int dwReadCount = 0;
                        while(dwReadCount < picdataLength)
                        {
                            dwReadCount += picfile.read(ptrpicByte.sData, dwReadCount, picdataLength - dwReadCount);
                        }
                        CommonMethod.WriteBuffToPointer(ptrpicByte.sData, pFileBuf);
                    }
                } catch (IOException ex) {
                    ex.printStackTrace();
                }

            }
            //byAct == 2（delete file）No example
            return true;
        }
    }

    //Storage callback: The SDK will save the relationship between the image url and the file path through the built-in
    // Sqlite database, without the user's own maintenance, the user only needs to save the file.
    //But due to the performance limitations of sqlite, its read and write efficiency is not high
	private class FEHomeSSStorageCallBack implements EHomeSSStorageCallBack {
		public boolean invoke(NativeLong iHandle, String pFileName, Pointer pFileBuf, int dwFileLen, Pointer pFilePath,
				Pointer pUser) {
			if (pFileName == null || pFileBuf == null || dwFileLen == 0) {
				return false;
			}
			
			File mySSPath = new File(SS_PATH);
			if ( !mySSPath.exists()){
				mySSPath.mkdir();
		           System.out.println("Creat dictionary:"+ SS_PATH);
			}
			
			File mySSStoragePath = new File(SS_STORAGE_PATH);
			if ( !mySSStoragePath.exists()){
				mySSStoragePath.mkdir();
		           System.out.println("Creat dictionary:"+ SS_STORAGE_PATH);
			}
			
			String sFileName = SS_STORAGE_PATH + "\\" + pFileName.replace(":", "_").replace("/", "_");
			try {
				File fw=new File(sFileName);
				InputStream bis = new ByteArrayInputStream(pFileBuf.getByteArray(0, dwFileLen));
				OutputStream out = new FileOutputStream(fw);
				//fw.write(CommonMethod.byteToString(pFileBuf.getByteArray(0, dwFileLen)));
				int count = 0;
				byte[] buf = new byte[8 * 1024];
				while( (count=bis.read(buf)) != -1 ) {
					out.write(buf, 0, count);
				}
				out.close();

				StringPointer spFilePath = new StringPointer(sFileName);
				spFilePath.write();
				CommonMethod.PointerCopy(spFilePath.getPointer(), pFilePath, sFileName.length());
			} catch (IOException e) {
				e.printStackTrace(); 
				System.out.println("StorageCallback Exception, filePath: " + sFileName);
				return false;
			}
			
			return true;
		}
	}

	//Message callback is used when the picture server needs to upload to the user, and the user needs to confirm certain parameters
    // tomcat -> return picture url
    // vrb -> file code
    // KMS -> kmsUserName & kmsUserPasswd
    // CloudStorage -> AK & SK
	private class FEHomeSSMsgCallBack implements EHomeSSMsgCallBack {
		public boolean invoke(NativeLong iHandle, int enumType, Pointer pOutBuffer, int dwOutLen, Pointer pInBuffer,
				int dwInLen, Pointer pUser) {
			switch (enumType) {
			case ISUPSSByJNA.NET_EHOME_SS_MSG_TOMCAT:
			{
				NET_EHOME_SS_TOMCAT_MSG struTomcatMsg = new NET_EHOME_SS_TOMCAT_MSG();				
				try{
					String sClassName = NET_EHOME_SS_TOMCAT_MSG.class.getName();
					struTomcatMsg = (NET_EHOME_SS_TOMCAT_MSG) CommonMethod.WritePointerDataToClass(pOutBuffer, sClassName);
				}catch(Exception ex){
					ex.printStackTrace();
				}
				
				int dwPicNum = struTomcatMsg.dwPicNum; 
				byte[] byPicUri = new byte[128 * 4];
//				for(int i = 0; i < dwPicNum; i++)
//				{
//					System.arraycopy(struTomcatMsg.pPicURLs, i * ISUPSSByJNA.MAX_URL_LEN_SS, byPicUri, i * ISUPSSByJNA.PIC_URI_LEN, ISUPSSByJNA.PIC_URI_LEN);
//
//				}
                CommonMethod.ByteCopy(struTomcatMsg.pPicURLs, byPicUri);

				String szUrlHead = CommonMethod.byteToString(struTomcatMsg.szDevUri);
				String szPicUrl = CommonMethod.byteToString(byPicUri);
				String szMsg = "tomcat:url" + szUrlHead + ", picNum: " + dwPicNum + ", picInfo:" + szPicUrl;
				CommonMethod.logRecord("INFO", "[NET_EHOME_SS_MSG_TOMCAT]->" + szMsg);
				
				File mySSPath = new File(SS_PATH);
				if ( !mySSPath.exists()){
					mySSPath.mkdir();
			           System.out.println("Creat dictionary:"+ SS_PATH);
				}
				
				File mySSMessagePath = new File(SS_MESSAGE_PATH);
				if ( !mySSMessagePath.exists()){
					mySSMessagePath.mkdir();
			           System.out.println("Creat dictionary:"+ SS_MESSAGE_PATH);
				}
				String sFileName = SS_MESSAGE_PATH + "\\tomcatOutput.txt";
				
				try {
					FileWriter fw = new FileWriter(sFileName, true);
					
//					for(int i = 0; i < dwPicNum; i++)
//					{
//						fw.write(szPicUrl, szPicUrl.length() + i * ISUPSSByJNA.PIC_URI_LEN, ISUPSSByJNA.PIC_URI_LEN);
//						fw.write("\n");
//					}
                    fw.write(szPicUrl, 0, szPicUrl.length());
                    fw.write("\n");
					fw.close();
					
				} catch (IOException e) {
					e.printStackTrace();  
				}
				
				break;
			}
			case ISUPSSByJNA.NET_EHOME_SS_MSG_KMS_USER_PWD:
			{
				StringPointer spInBuff = new StringPointer("1");
				spInBuff.write();
				pInBuffer = spInBuff.getPointer();
			}
			case ISUPSSByJNA.NET_EHOME_SS_MSG_CLOUD_AK:
			{
				byte[] byTemp = new byte[128];
				System.arraycopy(pOutBuffer.getByteArray(0, dwOutLen), 0, byTemp, 0, dwOutLen);
				String sSecKey = CommonMethod.byteToString(CommonMethod.GetServerInfo().byClouldSecretKey);
				StringPointer spInBuff = new StringPointer(sSecKey);
				spInBuff.write();
				CommonMethod.PointerCopy(spInBuff.getPointer(), pInBuffer, sSecKey.length());
				break;
			}
			default:
				break;
			}
			return true;
		}
	}

    private boolean CreateSSInstance()
    {
        if(m_SSInstance == null)
        {
            synchronized (ISUPAMSByJNA.class)
            {
                String strDllPath = "";
                try
                {
                    //System.setProperty("jna.debug_load", "true");
                    if(CommonMethod.isWindows())
                        strDllPath = CommonMethod.GetLibPathByArch() + "HCISUPSS";
                    else if(CommonMethod.isLinux())
                        strDllPath = CommonMethod.GetLibPathByArch() + "libHCISUPSS.so";
                    m_SSInstance = (ISUPSSByJNA) Native.loadLibrary(strDllPath, ISUPSSByJNA.class);
                }catch (Exception ex) {
                    System.out.println("loadLibrary: " + strDllPath + " Error: " + ex.getMessage());
                    return false;
                }
            }
        }
        return true;
    }

    public static ISUPSSByJNA GetSSInstance()
    {
        return m_SSInstance;
    }

    public boolean IsRunning(){return m_bIsRunning;}
}
